//The MIT License

//Copyright (c) 2009 Lluis Sanchez, Nick Darnell

//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:

//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.

//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.

using System;
using System.IO;
using System.Collections;
using System.Reflection.Emit;
using System.Reflection;
using System.Collections.Generic;
using Polymorph.Properties;

namespace Polymorph
{
    public class CodeMethod
    {
        private MethodBase methodBase;
        private CodeBuilder builder;
        private string name;
        private MethodAttributes attributes;
        private Type returnType;
        private TypeBuilder typeBuilder;
        private Type[] parameterTypes;

        internal static CodeMethod DefineMethod(CodeClass cls, string name, 
            MethodAttributes attributes, Type returnType, Type[] parameterTypes)
        {
            return new CodeMethod(cls, name, attributes, returnType, parameterTypes);
        }

        internal static CodeMethod DefineConstructor(CodeClass cls, MethodAttributes attributes, 
            Type[] parameterTypes)
        {
            return new CodeMethod(cls, attributes, parameterTypes);
        }

        internal CodeMethod(CodeClass cls, string name, MethodAttributes attributes, 
            Type returnType, Type[] parameterTypes)
        {
            this.typeBuilder = cls.TypeBuilder;
            this.name = name;
            this.attributes = attributes;
            this.returnType = returnType;
            this.parameterTypes = parameterTypes;

            methodBase = typeBuilder.DefineMethod(name, attributes, returnType, parameterTypes);
            builder = new CodeBuilder(cls);
        }

        CodeMethod(CodeClass cls, MethodAttributes attributes, Type[] parameterTypes)
        {
            this.typeBuilder = cls.TypeBuilder;
            this.attributes = attributes;
            this.parameterTypes = parameterTypes;
            this.name = typeBuilder.Name;

            methodBase = typeBuilder.DefineConstructor(attributes, CallingConventions.Standard, 
                parameterTypes);

            builder = new CodeBuilder(cls);
        }

        public TypeBuilder DeclaringType
        {
            get { return typeBuilder; }
        }

        public MethodInfo MethodInfo
        {
            get { return methodBase as MethodInfo; }
        }

        public MethodBase MethodBase
        {
            get { return methodBase; }
        }

        public string Name
        {
            get { return name; }
        }

        public MethodAttributes Attributes
        {
            get { return attributes; }
        }

        public Type ReturnType
        {
            get { return returnType; }
        }

        public Type[] ParameterTypes
        {
            get { return parameterTypes; }
        }

        public CodeBuilder CodeBuilder
        {
            get { return builder; }
        }

        public bool IsStatic
        {
            get { return (attributes & MethodAttributes.Static) != 0; }
        }

        public CodeArgumentReference GetArg(int index)
        {
            if (index < 0 || index >= parameterTypes.Length)
            {
                throw new InvalidOperationException(
                    string.Format(Resources.InvalidArgumentNumber0, index));
            }

            int narg = IsStatic ? index : index + 1;
            return new CodeArgumentReference(parameterTypes[index], narg, "arg" + index);
        }

        public CodeArgumentReference GetThis()
        {
            if (IsStatic)
                throw new InvalidOperationException(Resources.NoThisInStaticMethods);

            return new CodeArgumentReference(DeclaringType, 0, "this");
        }

        public void Generate()
        {
            ILGenerator gen = methodBase is MethodInfo ? 
                ((MethodBuilder)methodBase).GetILGenerator() : 
                ((ConstructorBuilder)methodBase).GetILGenerator();

            Label returnLabel = gen.DefineLabel();
            builder.ReturnLabel = returnLabel;
            builder.Generate(gen);
            gen.MarkLabel(returnLabel);
            gen.Emit(OpCodes.Ret);
        }

        public void UpdateMethodBase(Type type)
        {
            if (methodBase is MethodInfo)
            {
                methodBase = type.GetMethod(methodBase.Name, parameterTypes);
            }
            else if (methodBase.IsStatic)
            {
                methodBase = type.GetConstructor(
                    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static,
                    null, parameterTypes, null);
            }
            else
            {
                methodBase = type.GetConstructor(
                    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, 
                    null, parameterTypes, null);
            }
        }

        public string PrintCode()
        {
            StringWriter sw = new StringWriter();
            CodeWriter cw = new CodeWriter(sw);
            PrintCode(cw);
            return sw.ToString();
        }

        public void PrintCode(CodeWriter cp)
        {
            cp.BeginLine();
            if ((methodBase.Attributes & MethodAttributes.Static) != 0)
                cp.Write("static ");
            if (returnType != null) cp.Write(returnType + " ");
            cp.Write(name + " (");
            for (int n = 0; n < parameterTypes.Length; n++)
            {
                if (n > 0) cp.Write(", ");
                cp.Write(parameterTypes[n] + " arg" + n);
            }
            cp.Write(")");
            cp.EndLine();
            cp.WriteLineInd("{");

            builder.PrintCode(cp);

            cp.WriteLineUnind("}");
        }
    }
}